using System;
using System.Xml;
using System.Diagnostics;
using System.Collections;
using System.IO;
using System.Collections.Specialized;
using System.Text;
using Widgetsphere.Generator.Common.Util;
using Widgetsphere.Generator.Common;
using Widgetsphere.Generator.Common.Exceptions;

namespace Widgetsphere.Generator.Common.Logging
{
	public class WSLog
	{
		#region constants
		internal const string LOG_ERROR_SOURCE = "WidgetsphereLogger";
		internal const string LOG_SWITCHNAME = "Default";
		private const string ConfigName = "Widgetsphere.config.xml";
		#endregion

		#region member variables
		private static WSLog mInstance;
		private FileInfo mAppConfigFile;
		private FileSystemWatcher mAppFileWatcher;
		private string mExeName;
		private static ArrayList mLogClasses = new ArrayList();
		private static TraceSwitch CurrentSwitch;
		#endregion

		#region Setup and Initilize
		static WSLog()
		{
			try
			{
				mInstance = new WSLog(Process.GetCurrentProcess().ProcessName);
			}
			catch (Exception ex)
			{
				LogLogFailure("public static void InitializeWSLog(string exeName:" + Process.GetCurrentProcess().ProcessName + ")" + Environment.NewLine + ex.ToString());
			}
		}

		private WSLog(string exeName)
		{
			mExeName = exeName;
			CurrentSwitch = new TraceSwitch("Widgetsphere", "Widgetsphere trace switch for " + exeName);
			try
			{
				SetDefaults();
				InitializeConfigFile();
				mAppFileWatcher_Changed(this, new FileSystemEventArgs(WatcherChangeTypes.Changed, mAppConfigFile.DirectoryName, mAppConfigFile.Name));
			}
			catch (Exception ex)
			{
				LogLogFailure(ex.ToString());
			}
		}

		private void SetDefaults()
		{
			CurrentSwitch.Level = TraceLevel.Error;
			string logFileFullPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Widgetsphere\\" + mExeName + "Widgetspere.log");
			FileInfo logFile = new FileInfo(logFileFullPath);
			AddListener("MultiProcessFileTraceListener", typeof(MultiProcessFileTraceListener).FullName, logFile.FullName + "?5242880");
		}

		private void InitializeConfigFile()
		{

			mAppConfigFile = new FileInfo(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Widgetsphere\\" + ConfigName));
			if (!mAppConfigFile.Directory.Exists)
				mAppConfigFile.Directory.Create();
			if (!mAppConfigFile.Exists)
				mAppConfigFile.Create();
			mAppFileWatcher = new FileSystemWatcher(mAppConfigFile.DirectoryName, mAppConfigFile.Name);
			mAppFileWatcher.NotifyFilter = NotifyFilters.LastWrite;
			mAppFileWatcher.Changed += new FileSystemEventHandler(mAppFileWatcher_Changed);
			mAppFileWatcher.EnableRaisingEvents = true;
		}
		#endregion

		#region TraceStatements
		private static void Log(TraceLevel level, string message)
		{
			string traceLevelString;
			if (level == TraceLevel.Off)
			{
				traceLevelString = "Always";
				traceLevelString = traceLevelString.PadRight(7);
			}
			else
			{
				traceLevelString = level.ToString();
				traceLevelString = traceLevelString.PadRight(7);
			}
			string logString = String.Format("{0}:{1}: {2}", traceLevelString, DateTime.UtcNow.ToString("yyyyMMdd.HHmmss"), message);
			Trace.WriteLine(logString);
		}

		private static void Log(TraceLevel level, string message, object arg1)
		{
			string logString = String.Format(message, arg1);
			Log(level, logString);
		}

		private static void Log(TraceLevel level, string message, object arg1, object arg2)
		{
			string logString = String.Format(message, arg1, arg2);
			Log(level, logString);
		}

		private static void Log(TraceLevel level, string message, object arg1, object arg2, object arg3)
		{
			string logString = String.Format(message, arg1, arg2, arg3);
			Log(level, logString);
		}

		private static void Log(TraceLevel level, string message, object[] args)
		{
			string logString = String.Format(message, args);
			Log(level, logString);
		}
		#endregion

		#region LogAlways
		public static void LogAlways(string message)
		{
			Log(TraceLevel.Off, message);
		}

		public static void LogAlways(string message, object arg1)
		{
			Log(TraceLevel.Off, message, arg1);
		}

		public static void LogAlways(string message, object arg1, object arg2)
		{
			Log(TraceLevel.Off, message, arg1, arg2);
		}

		public static void LogAlways(string message, object arg1, object arg2, object arg3)
		{
			Log(TraceLevel.Off, message, arg1, arg2, arg3);
		}

		public static void LogAlways(string message, object[] args)
		{
			Log(TraceLevel.Off, message, args);
		}
		#endregion

		#region LogError
		public static void LogError(string message)
		{
			if (CurrentSwitch.TraceError)
			{
				Log(TraceLevel.Error, message);
			}
		}

		public static void LogError(string message, object arg1)
		{
			if (CurrentSwitch.TraceError)
			{
				Log(TraceLevel.Error, message, arg1);
			}
		}

		public static void LogError(string message, object arg1, object arg2)
		{
			if (CurrentSwitch.TraceError)
			{
				Log(TraceLevel.Error, message, arg1, arg2);
			}
		}

		public static void LogError(string message, object arg1, object arg2, object arg3)
		{
			if (CurrentSwitch.TraceError)
			{
				Log(TraceLevel.Error, message, arg1, arg2, arg3);
			}
		}

		public static void LogError(string message, object[] args)
		{
			if (CurrentSwitch.TraceError)
			{
				Log(TraceLevel.Error, message, args);
			}
		}


		public static void LogError(Exception ex)
		{
			LogError(ex.ToString());
		}

		public static void LogError(Exception ex, NameValueCollection error)
		{
			StringBuilder errorMsg = new StringBuilder();
			foreach (string name in error.Keys)
			{
				errorMsg.AppendFormat("{0} : {1}\n", name, error[name]);
			}
			errorMsg.Append(ex.ToString());
			LogError(errorMsg.ToString());
		}

		public static void LogError(Exception ex, string error)
		{
			LogError("{0}\n{1}", error, ex.ToString());
		}
		#endregion

		#region LogWarning
		public static void LogWarning(string message)
		{
			if (CurrentSwitch.TraceWarning)
			{
				Log(TraceLevel.Warning, message);
			}
		}

		public static void LogWarning(string message, object arg1)
		{
			if (CurrentSwitch.TraceWarning)
			{
				Log(TraceLevel.Warning, message, arg1);
			}
		}

		public static void LogWarning(string message, object arg1, object arg2)
		{
			if (CurrentSwitch.TraceWarning)
			{
				Log(TraceLevel.Warning, message, arg1, arg2);
			}
		}

		public static void LogWarning(string message, object arg1, object arg2, object arg3)
		{
			if (CurrentSwitch.TraceWarning)
			{
				Log(TraceLevel.Warning, message, arg1, arg2, arg3);
			}
		}

		public static void LogWarning(string message, object[] args)
		{
			if (CurrentSwitch.TraceWarning)
			{
				Log(TraceLevel.Warning, message, args);
			}
		}

		public static void LogWarning(Exception ex)
		{
			LogError(ex.ToString());
		}

		public static void LogWarning(Exception ex, string error)
		{
			LogError("{0}\n{1}", error, ex.ToString());
		}
		#endregion

		#region LogInfo
		public static void LogInfo(string message)
		{
			if (CurrentSwitch.TraceInfo)
			{
				Log(TraceLevel.Info, message);
			}
		}

		public static void LogInfo(string message, object arg1)
		{
			if (CurrentSwitch.TraceInfo)
			{
				Log(TraceLevel.Info, message, arg1);
			}
		}

		public static void LogInfo(string message, object arg1, object arg2)
		{
			if (CurrentSwitch.TraceInfo)
			{
				Log(TraceLevel.Info, message, arg1, arg2);
			}
		}

		public static void LogInfo(string message, object arg1, object arg2, object arg3)
		{
			if (CurrentSwitch.TraceInfo)
			{
				Log(TraceLevel.Info, message, arg1, arg2, arg3);
			}
		}

		public static void LogInfo(string message, object[] args)
		{
			if (CurrentSwitch.TraceInfo)
			{
				Log(TraceLevel.Info, message, args);
			}
		}
		#endregion

		#region LogVerbose
		public static void LogVerbose(string message)
		{
			if (CurrentSwitch.TraceVerbose)
			{
				Log(TraceLevel.Verbose, message);
			}
		}

		public static void LogVerbose(string message, object arg1)
		{
			if (CurrentSwitch.TraceVerbose)
			{
				Log(TraceLevel.Verbose, message, arg1);
			}
		}

		public static void LogVerbose(string message, object arg1, object arg2)
		{
			if (CurrentSwitch.TraceVerbose)
			{
				Log(TraceLevel.Verbose, message, arg1, arg2);
			}
		}

		public static void LogVerbose(string message, object arg1, object arg2, object arg3)
		{
			if (CurrentSwitch.TraceVerbose)
			{
				Log(TraceLevel.Verbose, message, arg1, arg2, arg3);
			}
		}

		public static void LogVerbose(string message, object[] args)
		{
			if (CurrentSwitch.TraceVerbose)
			{
				Log(TraceLevel.Verbose, message, args);
			}
		}
		#endregion

		#region Log Log Failure
		public static void LogLogFailure(string message)
		{
			string logName = "Application";
			if (!EventLog.SourceExists(LOG_ERROR_SOURCE))
			{
				EventLog.CreateEventSource(LOG_ERROR_SOURCE, logName);
			}
			else
			{
				logName = EventLog.LogNameFromSourceName(LOG_ERROR_SOURCE, System.Environment.MachineName);
			}
			EventLog applicationLog = new EventLog(logName);
			applicationLog.Source = LOG_ERROR_SOURCE;
			applicationLog.WriteEntry(message, EventLogEntryType.Warning);
		}
		#endregion

		#region File System Watcher
		private void mAppFileWatcher_Changed(object sender, FileSystemEventArgs e)
		{
			try
			{
				WSLog.LogVerbose("Called: WSLog.mAppFileWatcher_Changed(object sender, FileSystemEventArgs e)");
				XmlDocument oDoc = new XmlDocument();
				if (mAppConfigFile.Exists)
				{
					oDoc.Load(mAppConfigFile.FullName);
					ResetProperties(oDoc);
					ResetSwitches(oDoc);
					ResetListeners(oDoc);
				}
			}
			catch (Exception ex)
			{
				WSLog.LogLogFailure("Configuration File Cannot Be Parsed Change Was Invalid\n" + ex.ToString());
			}
		}

		private void ResetProperties(XmlDocument oDoc)
		{
			WSLog.LogVerbose("Called: private void ResetProperties(XmlDocument oDoc)");

			XmlNode traceNode = oDoc.DocumentElement.SelectSingleNode("application[@name = \"" +
				mExeName + "\"]/system.diagnostics/trace");

			if (traceNode != null)
			{
				if (traceNode.Attributes["autoflush"] != null)
				{
					try { Trace.AutoFlush = bool.Parse(traceNode.Attributes["autoflush"].Value); }
					catch { Trace.AutoFlush = false; }
				} // end if

				if (traceNode.Attributes["indentsize"] != null)
				{
					try { Trace.IndentSize = int.Parse(traceNode.Attributes["indentsize"].Value); }
					catch { Trace.IndentSize = 0; }
				} // end if
			} // end if
		} // end ResetProperties

		private void ResetSwitches(XmlDocument oDoc)
		{
			WSLog.LogVerbose("Called: private void ResetSwitches(XmlDocument oDoc)");
			XmlNode switchesNode = oDoc.DocumentElement.SelectSingleNode("application[@name = \"" +
				mExeName + "\"]/system.diagnostics/switches");

			if (switchesNode != null)
			{
						XmlNode switchNode = switchesNode.SelectSingleNode("add[@name = \"" + LOG_SWITCHNAME + "\"]");
						if (switchNode == null)
							CurrentSwitch.Level = TraceLevel.Off;
						else
							CurrentSwitch.Level = (TraceLevel)int.Parse(switchNode.Attributes["value"].Value);
			}

		}

		private void ResetListeners(XmlDocument oDoc)
		{
			WSLog.LogVerbose("Called: private void ResetListeners(XmlDocument oDoc)");

			XmlNode listenersNode = oDoc.DocumentElement.SelectSingleNode("application[@name = \"" +
				mExeName + "\"]/system.diagnostics/trace/listeners");

			if (listenersNode != null)
			{
				WSLog.LogInfo("Pre Listener Count : {0}", listenersNode.ChildNodes.Count);
				if (listenersNode != null)
				{
					RemoveListeners(listenersNode);
					AddListeners(listenersNode);
				}
			}
			else
			{
				RemoveListeners();
			}
			if (listenersNode != null)
			{
				WSLog.LogInfo("Post Listener Count : {0}", listenersNode.ChildNodes.Count);
			}
			else
			{
				WSLog.LogLogFailure("Post Listener Count : 0");
			}
		}

		private void RemoveListeners()
		{
			for (int i = Trace.Listeners.Count - 1; i >= 0; --i)
			{
				TraceListener listener = Trace.Listeners[i];
				listener.Flush();
				Trace.Listeners.Remove(Trace.Listeners[i]);
				listener.Dispose();
			}
		}

		private void RemoveListeners(XmlNode listenersNode)
		{
			for (int i = Trace.Listeners.Count - 1; i >= 0; --i)
			{
				TraceListener listener = Trace.Listeners[i];
				if (!ListenerExists(listener.Name, listenersNode))
				{
					listener.Flush();
					Trace.Listeners.Remove(Trace.Listeners[i]);
					listener.Dispose();
				}
			}
		}

		private void AddListeners(XmlNode listenersNode)
		{
			foreach (XmlNode listenerNode in listenersNode.ChildNodes)
			{
				string listenerName = string.Empty;
				try
				{
					listenerName = listenerNode.Attributes["name"].Value;
					string typeString = listenerNode.Attributes["type"].Value;
					XmlAttribute initializationDataAttribute = listenerNode.Attributes["initializeData"];
					if (!ListenerExists(listenerName))
					{
						AddListener(listenerName, typeString, initializationDataAttribute);
					}
				}
				catch (Exception ex)
				{
					WSLog.LogWarning(ex, "Cannot Add One Of The Listeners: " + listenerName);
				}
			}

		}

		private void AddListener(string listenerName, string typeString, XmlAttribute initializationDataAttribute)
		{
			if (initializationDataAttribute != null)
			{
				string initializationData = initializationDataAttribute.Value;
				AddListener(listenerName, typeString, initializationData);
			}
			else
			{
				AddListener(listenerName, typeString);
			}
		}

		private void AddListener(string listenerName, string typeString)
		{
			TraceListener newListener = CreateListener(listenerName, typeString);
			if (newListener != null)
				Trace.Listeners.Add(newListener);
		}

		private void AddListener(string listenerName, string typeString, string initializationData)
		{
			TraceListener newListener = CreateListener(listenerName, typeString, initializationData);
			if (newListener != null)
				Trace.Listeners.Add(newListener);
		}

		private bool ListenerExists(string listenerName, XmlNode listenersNode)
		{
			XmlNode testNode = listenersNode.SelectSingleNode(String.Format("add[@name = \"{0}\"]", listenerName));
			return !(testNode == null);
		}

		private bool ListenerExists(string listenerName)
		{
			bool retVal = false;
			foreach (TraceListener tl in Trace.Listeners)
			{
				if (StringHelper.Match(listenerName, tl.Name))
				{
					retVal = true;
				}
			}
			return retVal;
		}

		private TraceListener CreateListener(string listenerName, string typeString, string initializationData)
		{
			WSLog.LogVerbose("CreateListener(string listenerName:{0}, string typeString:{1}, string initializationData:{2})", listenerName, typeString, initializationData);
			TraceListener retVal = null;

			if (typeString == ("System.Diagnostics.TextWriterTraceListener"))
			{
				retVal = new TextWriterTraceListener(initializationData);
			}
			else if (typeString == ("System.Diagnostics.EventLogTraceListener"))
			{
				retVal = new EventLogTraceListener(initializationData);
			}
			else
			{
				retVal = (TraceListener)ReflectionHelper.CreateInstance(Type.GetType(typeString), new object[] { initializationData });
			}
			if (retVal != null)
			{
				retVal.Name = listenerName;
			}
			else
			{
				throw new Widgetsphere.Generator.Common.Exceptions.WidgetsphereException(String.Format("Could not create listener - listenerName:{0}- typeString:{1})", listenerName, typeString));
			}

			return retVal;
		}


		private TraceListener CreateListener(string listenerName, string typeString)
		{
			WSLog.LogVerbose("CreateListener(string listenerName:{0}, string typeString:{1})", listenerName, typeString);
			TraceListener retVal = null;

			if (typeString == ("System.Diagnostics.DefaultTraceListener"))
			{
				retVal = new DefaultTraceListener();
			}
			else if (typeString == ("System.Diagnostics.EventLogTraceListener"))
			{
				retVal = new EventLogTraceListener();
			}
			else
			{
				retVal = (TraceListener)ReflectionHelper.CreateInstance(Type.GetType(typeString));
			}

			if (retVal != null)
			{
				retVal.Name = listenerName;
			}
			else
			{
				throw new Widgetsphere.Generator.Common.Exceptions.WidgetsphereException(String.Format("Could not create listener - listenerName:{0}- typeString:{1})", listenerName, typeString));
			}
			return retVal;
		}
		#endregion

		public static WSTraceSwitch WidgetsphereGeneratedCode
		{
			get
			{
				return new WSTraceSwitch("Test", "Test");
			}
		}

	}

	#region class WSTraceSwitch
	public class WSTraceSwitch : TraceSwitch
	{
		internal WSTraceSwitch(string name, string description) : base(name, description) { }
	}
	#endregion

}

#if UNITTEST
namespace Widgetsphere.Generator.Common.Util.UnitTest
{
	using NUnit.Framework;	
	
	[TestFixture] 
	public class WSLogTester
	{
#region Class's Log Switch
		private static readonly WSTraceSwitch mLogSwitch = WSLog.WidgetsphereCommonUtil;
		#endregion

		[Test]
		public void TestLogError()
		{
			WSLog.LogError("Test Log Always Passed");
		}

		[Test]
		public void TestLogWarning()
		{
			WSLog.LogWarning("Test Log Warning Passed");
		}

		[Test]
		public void TestLogInfo()
		{
			WSLog.LogInfo("Test Log Info Passed");
		}

		[Test]
		public void TestLogVerbose()
		{
			WSLog.LogVerbose("Test Log Verbose Passed");
		}

		[Test]
		public void TestFileSystemWatcher()
		{
			WSLog.LogAlways("Test: WSLogTester.TestFileSystemWatcher()");
			FileInfo appConfigFile = new FileInfo(System.AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
			XmlDocument oDoc = new XmlDocument();
			oDoc.Load(appConfigFile.FullName);
			XmlNode switchesNode = oDoc.SelectSingleNode("configuration/system.diagnostics/switches");
			string holdInnerXml = switchesNode.InnerXml;
			switchesNode.InnerXml = "<add name=\"WidgetsphereCommonUtil\" value=\"4\" />";
			oDoc.Save(appConfigFile.FullName);
		}

	}
}
#endif

